﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;
using Provausio.Common.Utility;

namespace Provausio.Common.Utility
{
    public static class Serialization
    {
        /// <summary>
        /// SerializeXml object to string
        /// </summary>
        /// <param name="obj">Properly annotated object to be serialized</param>
        /// <param name="format">Output format of serialization. Optional parameter defaults to plain xml</param>
        /// <param name="serializer">Optional serializer type. Defaults to XmlSerializer</param>
        /// <param name="namespaces">Optional list of namespaces to add to root</param>
        /// <returns>Serialized object</returns>
        public static string SerializeXml(object obj, SerializationFormat format, Serializer serializer = Serializer.XmlSerializerSerializer, Dictionary<string, string> namespaces = null)
        {
            switch (format)
            {
                case SerializationFormat.PlainXml:
                    return SerializeToXml(obj, serializer, namespaces);
                case SerializationFormat.Base64EncodedXml:
                    return SerializeToXmlBase64(obj, serializer, namespaces);
                default:
                    return string.Empty;
            }
        }
        private static string SerializeToXml(object obj, Serializer serializer = Serializer.XmlSerializerSerializer, Dictionary<string, string> namespaces = null)
        {
            switch (serializer)
            {
                case Serializer.XmlSerializerSerializer:
                    return obj.SerializeByXmlSerializer(namespaces);
                default:
                    return String.Empty;
            }
        }
        private static string SerializeToXmlBase64(object obj, Serializer serializer = Serializer.XmlSerializerSerializer, Dictionary<string, string> namespaces = null)
        {
            var xml = SerializeToXml(obj, serializer, namespaces);
            var bytes = Encoding.UTF8.GetBytes(xml);
            return Convert.ToBase64String(bytes);
        }

        /// <summary>
        /// Deserializes xml string into .net object
        /// </summary>
        /// <typeparam name="T">.net object Type</typeparam>
        /// <param name="data">xml data</param>
        /// <param name="format">The format which the xml is encoded. Optional parameter defaults to plain xml</param>
        /// <param name="serializer">Type of serializer to user. Defaults to XmlSerializer</param>
        /// <returns>Object of type T</returns>
        public static T DeserializeXml<T>(string data, SerializationFormat format = SerializationFormat.PlainXml, Serializer serializer = Serializer.XmlSerializerSerializer)
        {
            switch (format)
            {
                case SerializationFormat.PlainXml:
                    return DeserializeFromXml<T>(data, serializer);
                case SerializationFormat.Base64EncodedXml:
                    return DeserializeFromXmlBase64<T>(data, serializer);
                default:
                    return default(T);
            }
        }
        private static T DeserializeFromXml<T>(string xmlData, Serializer serializer = Serializer.XmlSerializerSerializer)
        {
            switch (serializer)
            {
                case Serializer.XmlSerializerSerializer:
                    return xmlData.DeserializeByXmlSerializer<T>();
                default:
                    return default(T);
            }
        }
        private static T DeserializeFromXmlBase64<T>(string base64Data, Serializer serializer = Serializer.XmlSerializerSerializer)
        {
            //TODO: switch
            var bytes = Convert.FromBase64String(base64Data);
            var fromBytes = Encoding.UTF8.GetString(bytes);
            return fromBytes.DeserializeByXmlSerializer<T>();
        }

        //TODO: Create Serializers for DataContracts

        #region Strings

        static string SerializeByXmlSerializer(this object obj, Dictionary<string, string> ns = null)
        {
            var doc = new XDocument();

            var xmlns = new XmlSerializerNamespaces();
            if (ns == null) xmlns.Add("", ""); //ensures a clean root element
            else
            {
                foreach (var n in ns)
                {
                    xmlns.Add(n.Key, n.Value);
                }
            }

            using (var xmlWriter = doc.CreateWriter())
            {
                var xmlSerializer = new XmlSerializer(obj.GetType(), "");
                xmlSerializer.Serialize(xmlWriter, obj, xmlns);
                xmlWriter.Close();
            }
            return doc.ToString(SaveOptions.DisableFormatting);
        }
        internal static T DeserializeByXmlSerializer<T>(this string xml)
        {
            var xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(xml);
            if (xmlDoc.DocumentElement == null) return default(T);

            var reader = new XmlNodeReader(xmlDoc.DocumentElement);
            var xs = new XmlSerializer(typeof(T));
            return (T)xs.Deserialize(reader);
        }

        #endregion
    }

    public enum Serializer
    {
        DataContractSerializer,
        XmlSerializerSerializer
    }

    public enum SerializationFormat
    {
        PlainXml,
        PlainJson,
        Base64EncodedXml,
        Base64EncodedJson
    }
}
